package heap;

import jdk.internal.org.objectweb.asm.*;
import lombok.SneakyThrows;
import util.UnsafeAccessor;

import java.lang.invoke.*;

import static jdk.internal.org.objectweb.asm.Opcodes.*;

/**
 * https://www.bilibili.com/video/BV16E411n7Gk
 * https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
 * 方法区和运行时常量池溢出
 * <p>
 * 由于运行时常量池是方法区的一部分，所以这两个区域的溢出测试可以放到一起进行。前面曾经
 * 提到 HotSpot 从 JDK 7 开始逐步“去永久代”的计划，并在 JDK 8 中完全使用元空间来代替永久代的背景
 * 故事，在此我们就以测试代码来观察一下，使用“永久代”还是“元空间”来实现方法区，对程序有什么实际的影响。
 * <p>
 * String::intern() 是一个本地方法，它的作用是如果字符串常量池中已经包含一个等于此 String 对象的
 * 字符串，则返回代表池中这个字符串的 String 对象的引用；否则，会将此 String 对象包含的字符串添加
 * 到常量池中，并且返回此 String 对象的引用。在 JDK 6 或更早之前的 HotSpot 虚拟机中，常量池都是分配
 * 在永久代中，我们可以通过 -XX：PermSize 和 -XX：MaxPermSize 限制永久代的大小，即可间接限制其
 * 中常量池的容量。
 */
public class T03_StringPool {
    @SneakyThrows
    public static void main(String[] args) {
        demo1();
        demo2();
        demo3();
        demo4();
        demo5();
    }

    /**
     * @see java.lang.System#registerNatives()
     */
    public static void demo1() {
        String str1 = new StringBuffer("58").append("TC").toString();
        System.out.println(str1);
        System.out.println(str1.intern());
        System.out.println(str1 == str1.intern());

        System.out.println();

        String str2 = new StringBuffer("ja").append("va").toString();
        System.out.println(str2);
        System.out.println(str2.intern());
        System.out.println(str2 == str2.intern());
    }

    public static void demo2() {
        // 使用 char[] 数组配合 new 来创建
        String s1 = new String(new char[]{'a', 'b', 'c'});

        // 使用 byte[] 数组配合 new 来创建
        String s2 = new String(new byte[]{97, 98, 99});

        // 使用 int[] 数组配合 new 来创建
        String s3 = new String(new int[]{0x1F602}, 0, 1);

        // 使用 已有字符串配合 new 来创建
        String s4 = new String(new char[]{'a', 'b', 'c'});
        String s5 = new String(s4);

        // 使用字面量创建(不使用 new)，非对象，懒加载，不重复(不同类的常量池不共用，相同字面量在同一常量池只有一份。在堆上的对象只有一个)
        String s6 = "abc";
        // 说明：其中“abc”编译后会被加载到方法区中的运行时常量池中，执行到了代码区中的 adc 后才会利用常量池中的 abc 在堆内存中创建一个字符串对象，
        // 然后栈帧中的局部变量 s 会引用的对象
        final String s7 = "ab"; // 常量可以在编译期确定，变量不能
        String s8 = s7 + "c"; // 合二为一，使用 + 运算符来拼接创建，与 s6 使用的是同一字符串对象
    }

    public static void demo3() throws Throwable {
        String x = "b";

        // String s = "a" + x;
        // jdk9 以后，字符串拼接(InvokeDynamic)，默认编译器会生成如下等价的代码，jdk8 默认使用的是 StringBuilder
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        CallSite arbitrary = StringConcatFactory.makeConcatWithConstants(
                lookup,
                "arbitrary", // 方法名
                MethodType.methodType(String.class, String.class), // 方法签名
                "a\1" // 具体处方格式，\1 类似占位符
        );
        String s = (String) arbitrary.getTarget().invoke(x); // 反射调用
        System.out.println(s);
    }

    public static String concat(String x, String y) {
        return new StringBuilder().append(x).append(y).toString();
    }

    public static void demo4() throws Throwable { // 模仿字符串拼接
        String x = "hello,";
        String y = "world";

        MethodHandle concat = MethodHandles.lookup().findStatic(T03_StringPool.class, "concat",
                MethodType.methodType(String.class, String.class, String.class));
        String s = (String) concat.invoke(x, y);
        System.out.println(s);
    }

    public static void demo5() throws Throwable { // JDK 字符串拼接
        String x = "hello,";
        String y = "world";

        // 先加载，再初始化
        Class<?> innerClass = UnsafeAccessor.getUNSAFE().defineAnonymousClass(T03_StringPool.class, dump(), null);
        UnsafeAccessor.getUNSAFE().ensureClassInitialized(innerClass);

        // 反射使用
        MethodHandle concat = MethodHandles.lookup().findStatic(innerClass, "concat",
                MethodType.methodType(String.class, String.class, String.class));
        String s = (String) concat.invoke(x, y);
        System.out.println(s);
    }

    public static byte[] dump() throws Exception { // asm
        ClassWriter classWriter = new ClassWriter(0);
        FieldVisitor fieldVisitor;
        MethodVisitor methodVisitor;
        AnnotationVisitor annotationVisitor0;

        classWriter.visit(V11, ACC_SUPER, "heap/T03_StringPool", null, "java/lang/Object", null);
        classWriter.visitSource("T03_StringPool.java", null);

        {
            methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(1, label0);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitInsn(RETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("this", "Lheap/T03_StringPool;", null, label0, label1, 0);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "concat", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(3, label0);
            methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
            methodVisitor.visitInsn(DUP);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            methodVisitor.visitVarInsn(ALOAD, 1);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            methodVisitor.visitInsn(ARETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("x", "Ljava/lang/String;", null, label0, label1, 0);
            methodVisitor.visitLocalVariable("y", "Ljava/lang/String;", null, label0, label1, 1);
            methodVisitor.visitMaxs(2, 2);
            methodVisitor.visitEnd();
        }
        classWriter.visitEnd();

        return classWriter.toByteArray();
    }
}
